package event

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/simple-set/simple.io/src/socket"
	"io"
	"log/slog"
	"reflect"
	"time"
)

// StarterClient 客户端启动器接口
type StarterClient interface {
	Dial(addr string) (*Session, error)
	DialTimeout(addr string, timeout time.Duration) (*Session, error)
	Session() (session *Session)
	Socket() *socket.Socket
	Request(body any) (any, error)
	Close() error
}

// 客户端模式起动器
type starterClient struct {
	client     socket.Client
	clientName string
	session    *Session
	event      *Event
	ctx        context.Context
}

func newStarterClient(network string, event *Event, ctx context.Context) *starterClient {
	starter := &starterClient{event: event, ctx: ctx}
	if network == "tcp" {
		starter.client = socket.NewTcpClient()
	} else if network == "udp" {
		starter.client = socket.NewUdpClient()
	}
	starter.clientName = reflect.ValueOf(starter.client).Elem().Type().String()
	return starter
}

func (s *starterClient) Dial(addr string) (*Session, error) {
	return s.DialTimeout(addr, time.Second*30)
}

func (s *starterClient) DialTimeout(addr string, timeout time.Duration) (*Session, error) {
	slog.Debug(fmt.Sprintf("%s, connect to %s address", s.clientName, addr))
	if sock, err := s.client.DialTimeout(addr, timeout); err != nil {
		return nil, err
	} else {
		s.session = NewSessionContext(sock, s.ctx)
	}

	s.session.setState(Accept)
	if _, err := s.event.execute(s.session, nil); err != nil {
		slog.Error(fmt.Sprintf("%s, accept ", s.clientName), "client", addr, "err", err)
		_ = s.session.Close()
		return nil, err
	}
	s.session.setState(Active)
	return s.session, nil
}

func (s *starterClient) Request(body any) (any, error) {
	if s.session.state != Active {
		return nil, errors.New("session is not active")
	}
	result, err := s.event.execute(s.session, body)
	if err != nil && err != io.EOF {
		slog.Error(fmt.Sprintf("%s, Active ", s.clientName), "client", s.session.Socket.RemoteAddr().String(), "err", err)
		_ = s.session.Close()
	}
	return result, err
}

func (s *starterClient) Close() error {
	if s.session == nil {
		return errors.New("session is nil")
	}

	s.session.setState(Disconnect)
	if _, err := s.event.execute(s.session, nil); err != nil {
		slog.Error(fmt.Sprintf("%s, disconnect ", s.clientName), "client", s.session.Socket.RemoteAddr().String(), "err", err)
	}
	slog.Debug(fmt.Sprintf("%s, disconnect from %s", s.clientName, s.session.Socket.RemoteAddr().String()))
	return s.session.Close()
}

func (s *starterClient) Session() *Session {
	return s.session
}

func (s *starterClient) Socket() *socket.Socket {
	return s.session.Socket
}
